/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.scheduler.specific.pbs.status;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import org.apache.log4j.Logger;

import jp.aist.gtrc.plus.scheduler.node.NodeResource;
import jp.aist.gtrc.plus.scheduler.status.ArchType;
import jp.aist.gtrc.plus.scheduler.status.JobStatus;
import jp.aist.gtrc.plus.scheduler.status.NodeStatusBase;
import jp.aist.gtrc.plus.scheduler.status.OSType;


//	attr			value
//	-----------------------------
//	koume00.hpcc.jp	4
//		state			free
//		np				2
//		ntype			cluster
//		status			arch=linux,uname=Linux ....,sessions=4488,nsessions=1,
//						nusers=1,idletime=282,totmem=3128572kb,availmem=2963320kb,
//						physmem=1031436kb,ncpus=2,loadave=0.00,netload=4226236074,
//						state=free,rectime=1124952903
//	koume01.hpcc.jp
//		state   free
//		np      2
//		ntype   cluster
//		jobs    0/45.koume.hpcc.jp
//		status  ....
//	koume02.hpcc.jp
//		state = job-exclusive
//		np = 2
//		ntype = cluster
//		jobs = 0/51.koume.hpcc.jp, 1/52.koume.hpcc.jp
//	...


public class PBSNodeStatus extends NodeStatusBase implements StatusMap {

	private ArchType arch;
	private OSType os;
	private final StatusMap map;
	private HashMap<String, String> properties = null;

	protected static final Logger logger = Logger.getLogger(PBSNodeStatus.class);

	public PBSNodeStatus(String name){

		this.map = new StatusMapImpl(name);

	}

	public String getName() {
		return map.getName();
	}

	public String getState(){

		// "free", "down", "busy" etc
		return map.getStatus("state");

	}

	public int getCPUNum(){

		/*
		 * 'np' is physical CPU number.
		 * 		'np' value is specified at ${PBS_HOME}/server_priv/nodes file,
		 * 		such as "node0.example.com np=2".
		 * 		If you have SMP node, you must specify CPU num at nodes file.
		 * 		If not specified, Torque is always treated as single CPU node.
		 * 'ncpus' is logical CPU number, ncpus = 2 * np if HTT supported.
		 * 		On Linux system, Torque returns "processor" line number in
		 * 		/proc/cpuinfo.
		 */

		String n = getSystemInfo("ncpus");
		if (n != StatusMapImpl.UNKNOWN_STATUS) {
			return Integer.parseInt(n);
		} else {
			return Integer.parseInt(map.getStatus("np"));
		}

	}

        public String getProperties() {
                return map.getStatus("properties"); 
        }

	public String getType(){

		// "cluster", "shared" etc
		return map.getStatus("ntype");

	}

	public String getSystemInfo(String key){

		String s = map.getStatus("status");
		// s="arch=xxx,uname=xxx,..."

		for (String t : s.split(",")) {
			if (t.startsWith(key)) {
				// t is "arch=xxx" (if key is "arch")
				// u is "xxx" (plus 1 means length of "=")
				String u = t.substring(key.length() + 1);
				return u;
			}
		}

		logger.debug("unknown status: " + key);
		return StatusMapImpl.UNKNOWN_STATUS;

	}

	public double getLoadAverage(){

		String ave = getSystemInfo("loadave");
		if (ave != StatusMapImpl.UNKNOWN_STATUS){
			return Double.parseDouble(ave);
		} else {
			return 0.0;	// unknown
		}

	}

	public int getIdleTime() {

		String time = getSystemInfo("idletime");
		if (time != StatusMapImpl.UNKNOWN_STATUS){
			return Integer.parseInt(time);
		} else {
			return 0;	// unknown
		}

	}

	public long getPhysicalMemory(){

		String size = getSystemInfo("physmem");
		if (size != StatusMapImpl.UNKNOWN_STATUS){
			if (size.endsWith("kb"))
				size = size.substring(0, size.length()-2);
			return 1024 * Long.parseLong(size);
		} else {
			return 0;	// unknown
		}

	}

	public Collection<String> getRunningJobIDs() {

		LinkedList<String> jobIDs = new LinkedList<String>();
		String jobs = map.getStatus("jobs");
		if (jobs == StatusMapImpl.UNKNOWN_STATUS) {
			return jobIDs;
		}

		/*
		 * "jobs" attribute value format:
		 * 		"CPUID/JOBID, CPUID/JOBID, ...."
		 * ex:
		 * 		"0/1.pbs.server.com"
		 * 			The job of 1.pbs.server.com is running of CPU #0.
		 *
		 * 		"0/1.pbs.server.com, 1/2.pbs.server.com"
		 * 			The job of 1.pbs.server.com is running of CPU #0.
		 * 			The job of 2.pbs.server.com is running of CPU #1.
		 *
		 * This function returns list of jobId.
		 * 		ex: {"1.pbs.server.com", "1/2.pbs.server.com"}
		 */
		for (String entry : jobs.split(", ")) {
			String[] name = entry.split("/");
			if (name.length == 2) {
				jobIDs.add(name[1]);
			} else {
				assert(false);
			}
		}

		return jobIDs;

	}

	public int getRunningJobNum() {

		return getRunningJobIDs().size();

	}

	public boolean isAlive() {
                // The newly created node has state "down,offline" for 
                // a few seconds, and "offline" state  should be considered too.
                
		if (getState().equals("down")) {
                   return false;
                } else if (getState().equals("down,offline")) {
System.out.println("Found node status down,offline");
                   return false;
                } else if (getState().equals("offline")) {
                   return false;
                }
                return true;
	}

	public boolean isIdle() {

		return getState().equals("free");

	}

	private OSType makeOSType() {

		String uname = getSystemInfo("uname");

		if (uname.startsWith("Linux")) {
			/*
			 * uname= "Linux hostname 2.4.20-30.8.legacy.SCOREsmp "
			 * 		+ "#1 SMP Sat Jun 19 03:57:23 JST 2004 i686"
			 */
			String[] s = uname.split(" ");
			if (s.length >= 3) {
				String ver = s[2];
				if (ver.startsWith("2.4."))
					return OSType.Linux_24;
				if (ver.startsWith("2.6."))
					return OSType.Linux_26;
			}
			return OSType.Linux;
		} else if (uname.startsWith("SunOS")) {
			/*
			 * uname = "SunOS hostname 5.8 Generic_108529-13 i86pc"
			 * uname = "SunOS hostname 5.9 Generic_118558-10 sun4u"
			 * uname = "SunOS hostname 5.10 Generic i86pc"
			 */
			String[] s = uname.split(" ");
			if (s.length >= 3) {
				String ver = s[2];
				if (ver.equals("5.8"))
					return OSType.Solaris_8;
				if (ver.equals("5.9"))
					return OSType.Solaris_9;
				if (ver.equals("5.10"))
					return OSType.Solaris_10;
			}
			return OSType.Solaris;
		}

		return OSType.Unknown;

	}

	public OSType getOSType() {

		if (os == null)
			os = makeOSType();
		return os;

	}

	private ArchType makeArchType() {

		String uname = getSystemInfo("uname");

		if (uname.startsWith("Linux")) {
			/*
			 * arch		uname -m
			 * ------------------------
			 * 32bit x86	"i686" etc
			 * Opteron		"x86_64"
			 * Itanium		"ia64"
			 */
			if (uname.matches(".*i[3-6]86$"))
				return ArchType.x86;

			// ex: "Linux hostname 2.4.21-251-smp #1 SMP Sun Nov 7 22:25:59 JST 2004 x86_64"
			if (uname.endsWith("x86_64"))
				return ArchType.x86_64;

			// ex: "Linux hostname 2.4.21-score112-itanium2-smp #2 SMP Wed Feb 25 15:38:40 JST 2004 ia64"
			if (uname.endsWith("ia64"))
				return ArchType.ia64;

			// "sparc" or "sparc64"
			if (uname.indexOf("sparc") != -1)
				return ArchType.sparc;
		} else if (uname.startsWith("SunOS")) {
			String[] s = uname.split(" ");
			if (s.length == 5) {
				String arch = s[4];
				/*
				if (arch.equals("sun4d") || arch.equals("sun4u"))
					return ArchType.sparc64;
				*/
				if (arch.startsWith("sun"))
					return ArchType.sparc;
				// ex: "SunOS hostname 5.8 Generic_108529-13 i86pc"
				if (arch.equals("i86pc"))
					return ArchType.x86;
			}
		}

		return ArchType.unknown;

	}

	public ArchType getArchType() {

		if (arch == null)
			arch = makeArchType();
		return arch;

	}

	public NodeResource getResource() {

		return new NodeResource(this);

	}


	public String getAttribute(String attr) {

		/*
		 * NOTE: TORQUE doesn't have attribute value.
		 * Reply "true" if the attr exists, null if not.
		 */
		return getAttributeMap().get(attr);

	}

	private HashMap<String, String> makeAttributeMap() {

		HashMap<String, String> propMap = new HashMap<String, String>();
		String propList = map.getStatus("properties");
		if (propList != StatusMapImpl.UNKNOWN_STATUS) {
			for (String prop : propList.split(",")) {
				propMap.put(prop, NodeResource.PROP_EXISTS);
			}
		}
		return propMap;

	}

	public Map<String, String> getAttributeMap() {

		if (properties == null) {
			properties = makeAttributeMap();
		}
		return properties;

	}

	public Collection<JobStatus> getJobs() {
		// TODO Auto-generated method stub
		return null;
	}

	public StatusMap getStatusMap(String name) {
		return map.getStatusMap(name);
	}

	public void putStatus(String attr, String value) {
		map.putStatus(attr, value);
	}

	public String getStatus(String attr) {
		return map.getStatus(attr);
	}

	public String getStatus(String name, String attr) {
		return map.getStatus(name, attr);
	}

}
